package com.tfd.blog.service;

import com.tfd.blog.domain.Article;
import com.tfd.blog.domain.Label;
import com.tfd.blog.domain.LabelRel;
import com.tfd.blog.exception.BlogException;
import com.tfd.blog.exception.BlogExceptionCode;
import com.tfd.blog.repository.ArticleRepository;
import com.tfd.blog.repository.LabelRelRepository;
import com.tfd.blog.repository.LabelRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author TangFD@HF 2019/5/8
 */
@Service
public class LabelService {

    @Resource
    private ArticleRepository articleRepository;
    @Resource
    private LabelRepository labelRepository;
    @Resource
    private LabelRelRepository labelRelRepository;

    @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
    public void save(String articleId, List<String> labels) {
        if (StringUtils.isEmpty(articleId)) {
            throw new BlogException(BlogExceptionCode.ILLEGAL_ARGUMENT, "articleId not be empty!");
        }

        Article article = articleRepository.getOne(articleId);
        if (StringUtils.isEmpty(article.getId())) {
            throw new BlogException(BlogExceptionCode.ENTITY_NOT_EXIST, "article not exist by id [" + articleId + "]");
        }

        if (CollectionUtils.isEmpty(labels)) {
            labelRelRepository.deleteByObjId(articleId);
            return;
        }

        List<Label> labelList = labelRepository.findByNameIn(labels);
        if (CollectionUtils.isEmpty(labelList)) {
            labelRelRepository.deleteByObjId(articleId);
            saveLabels(articleId, labels);
            return;
        }

        List<String> needAddExistIds = new ArrayList<>();
        List<LabelRel> labelRelList = labelRelRepository.findByObjId(articleId);
        Map<String, Boolean> existLabelIdMap = labelRelList.stream().collect(Collectors.toMap(LabelRel::getLabelId,
                labelRel -> true, (a, b) -> b, () -> new HashMap<>(labelRelList.size())));
        Map<String, String> labelNameAndIdMap = labelList.stream().collect(Collectors.toMap(Label::getName, Label::getId,
                (a, b) -> b, () -> new HashMap<>(labelList.size())));
        for (Iterator<String> iterator = labels.iterator(); iterator.hasNext(); ) {
            String label = iterator.next();
            String labelId = labelNameAndIdMap.get(label);
            if (StringUtils.isEmpty(labelId)) {
                continue;
            }

            if (!existLabelIdMap.containsKey(labelId)) {
                needAddExistIds.add(labelId);
            }

            iterator.remove();
            existLabelIdMap.remove(labelId);
        }

        if (!CollectionUtils.isEmpty(labels)) {
            saveLabels(articleId, labels);
        }

        if (!CollectionUtils.isEmpty(needAddExistIds)) {
            saveLabelRels(articleId, needAddExistIds);
        }

        if (!CollectionUtils.isEmpty(existLabelIdMap)) {
            labelRelRepository.deleteByObjIdAndLabelIdIn(articleId, existLabelIdMap.keySet());
        }
    }

    private void saveLabelRels(String articleId, List<String> needAddExistIds) {
        Date date = new Date();
        List<LabelRel> labelRels = needAddExistIds.stream().map(id -> new LabelRel(articleId, id, date))
                .collect(Collectors.toCollection(() -> new ArrayList<>(needAddExistIds.size())));
        labelRelRepository.saveAll(labelRels);
    }

    private void saveLabels(String articleId, List<String> labels) {
        Date date = new Date();
        List<Label> list = labels.stream().map(label -> new Label(label, date)).collect(
                Collectors.toCollection(() -> new ArrayList<>(labels.size())));
        labelRepository.saveAll(list);
        List<LabelRel> labelRelList = list.stream().map(label -> new LabelRel(articleId, label.getId(), date))
                .collect(Collectors.toCollection(() -> new ArrayList<>(list.size())));
        labelRelRepository.saveAll(labelRelList);
    }

    public Map<String, List<String>> getArticleIdAndLabelNamesMap(Collection<String> articleIds) {
        if (CollectionUtils.isEmpty(articleIds)) {
            throw new IllegalArgumentException("articleIds not be empty!");
        }

        List<LabelRel> labelRelList = labelRelRepository.findByObjIdIn(articleIds);
        if (CollectionUtils.isEmpty(labelRelList)) {
            return new HashMap<>(0);
        }

        Set<String> labelIds = labelRelList.stream().map(LabelRel::getLabelId).collect(Collectors.toSet());
        Map<String, String> labelIdAndNameMap = getLabelIdAndNameMap(labelIds);
        if (CollectionUtils.isEmpty(labelIdAndNameMap)) {
            return new HashMap<>(0);
        }

        Map<String, List<String>> objIdAndLabelNamesMap = new HashMap<>(articleIds.size());
        labelRelList.forEach(labelRel -> {
            String name = labelIdAndNameMap.get(labelRel.getLabelId());
            if (StringUtils.isEmpty(name)) {
                return;
            }
            String objId = labelRel.getObjId();
            List<String> names = objIdAndLabelNamesMap.computeIfAbsent(objId, k -> new ArrayList<>());
            names.add(name);
        });

        return objIdAndLabelNamesMap;
    }

    public Map<String, String> getLabelIdAndNameMap(Set<String> labelIds) {
        List<Label> labels = labelRepository.findByIdIn(labelIds);
        if (CollectionUtils.isEmpty(labels)) {
            return new HashMap<>(0);
        }

        return labels.stream().collect(Collectors.toMap(Label::getId, Label::getName,
                (a, b) -> b, () -> new HashMap<>(labels.size())));
    }

    @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
    public void deleteById(String id) {
        labelRepository.deleteById(id);
        labelRelRepository.deleteByLabelId(id);
    }
}
